//this file is part of eMule
//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


//#include "stdafx.h"
#include "wintypes.h"
#include "emule.h"
#include "ListenSocket.h"
#include "opcodes.h"
#include "KnownFile.h"
#include "SharedFileList.h"
#include "UploadQueue.h"
#include "updownclient.h"
#include "ClientList.h"
#include "ServerSocket.h"

#if 0
#include "emule.h"
#include "packets.h"
#include "ServerSocket.h"
#include "emuleDlg.h"
#include "opcodes.h"
#include "SearchList.h"
#endif

#include <time.h>
//#include <afxmt.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
BEGIN_EVENT_TABLE(CServerSocket,wxSocketClient);
END_EVENT_TABLE()
*/

IMPLEMENT_DYNAMIC_CLASS(CServerSocket,CEMSocket)

CServerSocket::CServerSocket(CServerConnect* in_serverconnect)
{
  //printf("Serversocket: size %d\n",sizeof(CServerSocket));

	serverconnect = in_serverconnect;
	connectionstate = 0;
	cur_server = 0;
	info="";
	m_bIsDeleting = false;
	SetEventHandler(*theApp.emuledlg,ID_SOKETTI);
	SetNotify(wxSOCKET_CONNECTION_FLAG|wxSOCKET_INPUT_FLAG|wxSOCKET_OUTPUT_FLAG|wxSOCKET_LOST_FLAG);
	Notify(TRUE);
}

CServerSocket::~CServerSocket(){
  // remove event handler...
  SetNotify(0);
  Notify(FALSE);
	if (cur_server)
		delete cur_server;
	cur_server = NULL;
}

#include <errno.h>

void CServerSocket::OnConnect(int nErrorCode){
  //CAsyncSocket::OnConnect(nErrorCode);
  switch (errno){
  case EINPROGRESS:
    while(!WaitOnConnect(1,0)) {
    }
    if(!IsConnected()) {
    } else {
    }
    break;
  case 0:{   
    if (cur_server->HasDynIP()){
      struct sockaddr_in sockAddr;
      memset(&sockAddr, 0, sizeof(sockAddr));
      uint32 nSockAddrLen = sizeof(sockAddr);
      //GetPeerName((SOCKADDR*)&sockAddr,(int*)&nSockAddrLen);
      //GetPeer(sockAddr);
      wxIPV4address tmpaddr;
      GetPeer(tmpaddr);
      sockAddr.sin_addr.s_addr=GAddress_INET_GetHostAddress(tmpaddr.GetAddress());
      cur_server->SetID(sockAddr.sin_addr.s_addr);
      theApp.serverlist->GetServerByAddress(cur_server->GetAddress(),cur_server->GetPort())->SetID(sockAddr.sin_addr.s_addr);
    }
    SetConnectionState(CS_WAITFORLOGIN);
    break;
  }
#if 0    
  case EINPROGRESS: {
    printf("In progress.. please wait\n");
    wxIPV4address addr;
    addr.Hostname(cur_server->GetAddress());
    addr.Service(cur_server->GetPort());
    if (!this->Connect(addr,FALSE)){
      if(errno!=EINPROGRESS) {
	printf("connect error!!!\n");
      }
    }
    }
    return;
#endif
#if 0
  case WSAEADDRNOTAVAIL:
  case WSAECONNREFUSED:
  case WSAENETUNREACH:
  case WSAETIMEDOUT: 	
  case WSAEADDRINUSE:
    m_bIsDeleting = true;
    SetConnectionState(CS_SERVERDEAD);
    serverconnect->DestroySocket(this);
    return;
#endif
  case E2BIG:
    // hmm?? argument list too big
    m_bIsDeleting=true;
    SetConnectionState(CS_SERVERDEAD);
    serverconnect->DestroySocket(this);
    return;
  default:	
    m_bIsDeleting = true;
    SetConnectionState(CS_FATALERROR);
    serverconnect->DestroySocket(this);
    return;
  }	 
}

void CServerSocket::OnReceive(int nErrorCode){
	if (connectionstate != CS_CONNECTED && !this->serverconnect->IsConnecting()){
		serverconnect->DestroySocket(this);
		return;
	}
	CEMSocket::OnReceive(nErrorCode);

}

bool CServerSocket::ProcessPacket(char* packet, int32 size, int8 opcode){
	try{
		CServer* update;
		switch(opcode){
			case OP_SERVERMESSAGE:{
				char* buffer = new char[size-1];
				memcpy(buffer,&packet[2],size-2);
				buffer[size-2] = 0;
				
				CString message(buffer);
				if (message.Find("[emDynIP: ") != (-1) && message.Find("]") != (-1) && message.Find("[emDynIP: ") < message.Find("]")){
					CString dynip = message.Mid(message.Find("[emDynIP: ")+10,message.Find("]") - (message.Find("[emDynIP: ")+10));
					dynip.Trim(" ");
					if ( dynip.GetLength() && dynip.GetLength() < 51){
						CServer* eserver = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(),cur_server->GetPort());
						if (eserver){
							eserver->SetDynIP((char*)dynip.GetBuffer());
							cur_server->SetDynIP((char*)dynip.GetBuffer());
							theApp.emuledlg->serverwnd->serverlistctrl->RefreshServer(eserver);
						}
					}
				}
				theApp.emuledlg->AddServerMessageLine(buffer);
				delete[] buffer;
				break;
			}
			case OP_IDCHANGE:{
				if (size != sizeof(LoginAnswer_Struct)){
					throw GetResString(IDS_ERR_BADSERVERREPLY);
				}
				LoginAnswer_Struct* la = (LoginAnswer_Struct*) packet;
				if (la->clientid == 0){
//					SetConnectionState(CS_ERROR);
					uint8 state = theApp.glob_prefs->GetSmartIdState();
					if ( state > 0 ){
//						SetConnectionState(CS_ERROR);
						state++;
						if( state > 3 )
							theApp.glob_prefs->SetSmartIdState(0);
						else
							theApp.glob_prefs->SetSmartIdState(state);
					}
					break;
				}
				if( theApp.glob_prefs->GetSmartIdCheck() ){
					if (la->clientid >= 16777216 )
						theApp.glob_prefs->SetSmartIdState(1);
					else{
						uint8 state = theApp.glob_prefs->GetSmartIdState();
						if ( state > 0 ){
//							SetConnectionState(CS_ERROR);
							state++;
							if( state > 3 )
								theApp.glob_prefs->SetSmartIdState(0);
							else
								theApp.glob_prefs->SetSmartIdState(state);
							break;
						}
					}
				}
				if (connectionstate != CS_CONNECTED) {
					SetConnectionState(CS_CONNECTED);
					theApp.OnlineSig();       // Added By Bouc7 
				}
				serverconnect->SetClientID(la->clientid);
				theApp.emuledlg->AddLogLine(false,GetResString(IDS_NEWCLIENTID),la->clientid);

				for(POSITION pos = theApp.downloadqueue->filelist.GetHeadPosition(); pos != NULL;theApp.downloadqueue->filelist.GetNext(pos)) { 
					CPartFile* cur_file = theApp.downloadqueue->filelist.GetAt(pos); 
					if( cur_file->GetStatus() == PS_READY ){
						cur_file->ResumeFile();
					}
				}


				theApp.downloadqueue->filelist.GetCount();				
				break;
			}
			case OP_SEARCHRESULT:{
				theApp.emuledlg->searchwnd->LocalSearchEnd(theApp.searchlist->ProcessSearchanswer(packet,size));
				break;
			}
			case OP_FOUNDSOURCES:{
				CMemFile* sources = new CMemFile((BYTE*)packet,size);
				uchar fileid[16];
				sources->Read(fileid,16);
				if (CPartFile* file = theApp.downloadqueue->GetFileByID(fileid))
					file->AddSources(sources,cur_server->GetIP(), cur_server->GetPort());
				delete sources;
				break;
			}
			case OP_SERVERSTATUS:{
				// FIXME some statuspackets have a different size -> why? structur?
				if (size < 8)
					break;//throw "Invalid status packet";
				uint32 cur_user;
				memcpy(&cur_user,packet,4);
				uint32 cur_files;
				memcpy(&cur_files,packet+4,4);
				update = theApp.serverlist->GetServerByAddress( cur_server->GetAddress(), cur_server->GetPort() );
				if (update){
					update->SetUserCount(cur_user); 
					update->SetFileCount(cur_files);
					theApp.emuledlg->ShowUserCount(cur_user, cur_files);
					theApp.emuledlg->serverwnd->serverlistctrl->RefreshServer( update );
				}
				break;
			}
			//<<--Working, but needs to be cleaned up.. 
			case OP_SERVERIDENT:{ 
				if (size<38) { 
					theApp.emuledlg->AddLogLine(false,GetResString(IDS_ERR_KNOWNSERVERINFOREC)); 
					break;// throw "Invalid server info received"; 
				} 
				char* buffer = new char[size-29]; 
				CServer* update = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(),cur_server->GetPort()); 
				uint16 num,num2; 
				char* temp=new char[size-38+1]; 
				memcpy(buffer,&packet[30],size-30);// 1st 30 char contain only server address & fillers 
				memcpy(&num,&buffer[0],2); // length of server_name 
				memcpy(temp,&buffer[2],num); 
				temp[num]=0;//close the string 
				update->SetListName(temp); 
				memcpy(&num2,&buffer[num+6],2); 
				memcpy (temp,&buffer[num+8],num2); 
				temp[num2]=0; //close the string 
				update->SetDescription(temp); 
				theApp.emuledlg->ShowConnectionState(true,update->GetListName()); 
				theApp.emuledlg->serverwnd->serverlistctrl->RefreshServer(update); 
				delete[] temp; 
				delete[] buffer; 
				break;
			} 
			// tecxx 1609 2002 - add server's serverlist to own serverlist
			case OP_SERVERLIST:{
				{
					CSafeMemFile* servers = new CSafeMemFile((BYTE*)packet,size);
					unsigned char count;
					servers->Read(&count, 1);// check if packet is valid 
					if ((int32)(count*6 + 1) > size)	 // (servercount*(4bytes ip + 2bytes port) + 1byte servercount)
						count = 0;
					int addcount = 0;
					while(count)
					{
						uint32 ip;
						unsigned short port;
						servers->Read(&ip, 4);
						servers->Read(&port, 2);
						in_addr host;
						host.s_addr=ip;
						CServer* srv = new CServer(port, inet_ntoa(host));
						srv->SetListName(srv->GetFullIP());
						if (!theApp.emuledlg->serverwnd->serverlistctrl->AddServer(srv, true))
							delete srv;
						else
							addcount++;
						count--;
					}
					delete servers;
					if (addcount)
						theApp.emuledlg->AddLogLine(false,GetResString(IDS_NEWSERVERS), addcount);
				}
#if 0
				catch(CFileException* error){
					OUTPUT_DEBUG_TRACE();
					theApp.emuledlg->AddLogLine(false,GetResString(IDS_ERR_BADSERVERLISTRECEIVED));
					error->Delete();	//memleak fix
				}
#endif
				theApp.serverlist->SaveServermetToFile();
				break;
			}
			case OP_CALLBACKREQUESTED:{
				if (size == 6){
					uint32 dwIP;
					memcpy(&dwIP,packet,4);
					uint16 nPort;
					memcpy(&nPort,packet+4,2);
					CUpDownClient* client = theApp.clientlist->FindClientByIP(dwIP,nPort);
					if (client)
						client->TryToConnect();
					else{
						client = new CUpDownClient(nPort,dwIP,0,0,0);
						theApp.clientlist->AddClient(client);
						client->TryToConnect();
					}
				}
			}
			default:
				;
		}
		return true;
	}
	catch(CString error){
		theApp.emuledlg->AddLogLine(false,GetResString(IDS_ERR_PACKAGEHANDLING),error.GetBuffer());
		SetConnectionState(CS_DISCONNECTED);
		return false;
	}
}

void CServerSocket::ConnectToServer(CServer* server){
  cur_server = new CServer(server);
	theApp.emuledlg->AddLogLine(false,GetResString(IDS_CONNECTINGTO),cur_server->GetListName(),cur_server->GetFullIP(),cur_server->GetPort());
	SetConnectionState(CS_CONNECTING);

	//if (!this->Connect(server->GetAddress(),server->GetPort())){
	wxIPV4address addr;
	addr.Hostname(server->GetAddress());
	addr.Service(server->GetPort());
	if (!this->Connect(addr,FALSE)){
	  int error = errno; //this->GetLastError();
	  if ( error != EINPROGRESS){
	    theApp.emuledlg->AddLogLine(false,GetResString(IDS_ERR_CONNECTIONERROR),cur_server->GetListName(),cur_server->GetFullIP(),cur_server->GetPort(), error); 
	    SetConnectionState(CS_FATALERROR);
	    return;
	  }
	}
	info=server->GetListName();
	SetConnectionState(CS_CONNECTING);
}

void CServerSocket::OnError(int nErrorCode){
	SetConnectionState(CS_DISCONNECTED);
#ifdef _DEBUG
	theApp.emuledlg->AddLogLine(false,GetResString(IDS_ERR_SOCKET),cur_server->GetListName(),cur_server->GetFullIP(),cur_server->GetPort(), nErrorCode); 
#endif
}

void CServerSocket::PacketReceived(Packet* packet){
  ProcessPacket(packet->pBuffer,packet->size,packet->opcode);
}

void CServerSocket::OnClose(int nErrorCode){
	CEMSocket::OnClose(0);
	if (connectionstate == CS_CONNECTING){	 	
		SetConnectionState(CS_SERVERFULL);
	}
	else if (connectionstate == CS_CONNECTED){
		SetConnectionState(CS_DISCONNECTED);		
	}
	else{
		SetConnectionState(CS_NOTCONNECTED);
	}
	serverconnect->DestroySocket(this);	
}

void CServerSocket::SetConnectionState(sint8 newstate){
  sint8 oldstate = connectionstate;
  connectionstate = newstate;
  if (newstate < 1){
    serverconnect->ConnectionFailed(this);
  }
  else if (newstate == CS_CONNECTED || newstate == CS_WAITFORLOGIN){
    if (serverconnect) {
      serverconnect->ConnectionEstablished(this);
    }
  }
}
